package com.gitee.wsl.android.ui.ext

import android.view.Gravity
import android.view.View
import android.widget.LinearLayout
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.tabs.TabLayout
import timber.log.Timber
import java.lang.ref.WeakReference
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method


object TabLayoutExt {

}

interface TabTitleGenerator {
    fun getTitle(position: Int): String?
}

internal class FixedTabLayoutOnPageChangeListener(tabLayout: TabLayout) :
    OnPageChangeCallback() {
    private val mTabLayoutRef: WeakReference<TabLayout>
    private var mPreviousScrollState = 0
    private var mScrollState = 0

    init {
        mTabLayoutRef = WeakReference(tabLayout)
    }

    override fun onPageScrollStateChanged(state: Int) {
        mPreviousScrollState = mScrollState
        mScrollState = state
    }

    override fun onPageScrolled(
        position: Int, positionOffset: Float,
        positionOffsetPixels: Int
    ) {
        val tabLayout = mTabLayoutRef.get()
        if (tabLayout != null && shouldUpdateScrollPosition()) {
            // Update the scroll position, only update the text selection if we're being
            // dragged (or we're settling after a drag)
            tabLayout.setScrollPosition(position, positionOffset, true)
        }
    }

    override fun onPageSelected(position: Int) {
        val tabLayout = mTabLayoutRef.get()
        if (tabLayout != null && tabLayout.selectedTabPosition != position) {
            // Select the tab, only updating the indicator if we're not being dragged/settled
            // (since onPageScrolled will handle that).
            Internal.selectTab(
                tabLayout, tabLayout.getTabAt(position),
                mScrollState == ViewPager2.SCROLL_STATE_IDLE
            )
        }
    }

    private fun shouldUpdateScrollPosition(): Boolean {
        return mScrollState == ViewPager2.SCROLL_STATE_DRAGGING || (mScrollState == ViewPager2.SCROLL_STATE_SETTLING && mPreviousScrollState == ViewPager2.SCROLL_STATE_DRAGGING)
    }

    private object Internal {
        private var mMethodSelectTab: Method? = null

        init {
            mMethodSelectTab = getAccessiblePrivateMethod(
                TabLayout::class.java, "selectTab",
                TabLayout.Tab::class.java,
                Boolean::class.javaPrimitiveType!!
            )
        }

        @Throws(RuntimeException::class)
        private fun getAccessiblePrivateMethod(
            targetClass: Class<*>,
            methodName: String,
            vararg params: Class<*>
        ): Method {
            return try {
                val m: Method = targetClass.getDeclaredMethod(methodName, *params)
                m.isAccessible = true
                m
            } catch (e: NoSuchMethodException) {
                throw IllegalStateException(e)
            }
        }

        fun selectTab(tabLayout: TabLayout, tab: TabLayout.Tab?, updateIndicator: Boolean) {
            try {
                mMethodSelectTab?.invoke(tabLayout, tab, updateIndicator)
            } catch (e: IllegalAccessException) {
                Timber.e(IllegalStateException(e), e.message)
            } catch (e: InvocationTargetException) {
                throw handleInvocationTargetException(e)
            }
        }

        private fun handleInvocationTargetException(e: InvocationTargetException): RuntimeException {
            val targetException = e.targetException
            if (targetException is RuntimeException) {
                throw targetException
            } else {
                throw IllegalStateException(targetException)
            }
        }
    }
}

/**
 * This is a fork of the android-tablayouthelper project to make it compatible with Viewpager2.
 * See also https://github.com/h6ah4i/android-tablayouthelper/issues/13
 */
class TabLayoutHelper(
    var mTabLayout: TabLayout,
    var mViewPager: ViewPager2,
    var mTabTitleGenerator: TabTitleGenerator
) {

    private var mInternalOnTabSelectedListener: TabLayout.OnTabSelectedListener?
    private var mInternalTabLayoutOnPageChangeListener: FixedTabLayoutOnPageChangeListener?
    private var mInternalDataSetObserver: RecyclerView.AdapterDataObserver?
    private var mAdjustTabModeRunnable: Runnable? = null
    private var mSetTabsFromPagerAdapterRunnable: Runnable? = null
    private var mUpdateScrollPositionRunnable: Runnable? = null
    private var mAutoAdjustTabMode = false
    private var mDuringSetTabsFromPagerAdapter = false

    /**
     * Constructor.
     *
     * @param tabLayout         TabLayout instance
     * @param viewPager         ViewPager2 instance
     * @param tabTitleGenerator TabTitleGenerator instance
     */
    init {
        val adapter = mViewPager.adapter
            ?: throw IllegalArgumentException("ViewPager does not have a PagerAdapter set")

        mInternalDataSetObserver = object : RecyclerView.AdapterDataObserver() {
            override fun onChanged() {
                handleOnDataSetChanged()
            }
        }
        mInternalOnTabSelectedListener = object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab) {
                handleOnTabSelected(tab)
            }

            override fun onTabUnselected(tab: TabLayout.Tab) {
                // Do nothing
            }

            override fun onTabReselected(tab: TabLayout.Tab) {
                // Do nothing
            }
        }
        mInternalTabLayoutOnPageChangeListener = FixedTabLayoutOnPageChangeListener(mTabLayout)
        mViewPager.adapter!!.registerAdapterDataObserver(object :
            RecyclerView.AdapterDataObserver() {
            override fun onChanged() {
                setTabsFromPagerAdapter(mTabLayout, mViewPager.adapter, mViewPager.currentItem)
            }
        })
        setupWithViewPager(mTabLayout, mViewPager)
        setAutoAdjustTabModeEnabled(true)
    }

    /**
     * Sets auto tab mode adjustment enabled
     *
     * @param enabled True for enabled, otherwise false.
     */
    fun setAutoAdjustTabModeEnabled(enabled: Boolean) {
        if (mAutoAdjustTabMode == enabled) {
            return
        }
        mAutoAdjustTabMode = enabled
        if (mAutoAdjustTabMode) {
            adjustTabMode(-1)
        } else {
            cancelPendingAdjustTabMode()
        }
    }

    /**
     * Unregister internal listener objects, release object references, etc.
     * This method should be called in order to avoid memory leaks.
     */
    fun release() {
        cancelPendingAdjustTabMode()
        cancelPendingSetTabsFromPagerAdapter()
        cancelPendingUpdateScrollPosition()
        if (mInternalDataSetObserver != null) {
            mInternalDataSetObserver = null
        }
        if (mInternalOnTabSelectedListener != null) {
            mTabLayout.removeOnTabSelectedListener(mInternalOnTabSelectedListener!!)
            mInternalOnTabSelectedListener = null
        }
        if (mInternalTabLayoutOnPageChangeListener != null) {
            mInternalTabLayoutOnPageChangeListener = null
        }
    }

    /**
     * Override this method if you want to use custom tab layout.
     *
     * @param tabLayout TabLayout
     * @param position  Position of the item
     * @return TabLayout.Tab
     */
    private fun onCreateTab(tabLayout: TabLayout, position: Int): TabLayout.Tab {
        val tab = tabLayout.newTab()
        tab.text = mTabTitleGenerator.getTitle(position)
        return tab
    }

    fun setTabTitleGenerator(mTabTitleGenerator: TabTitleGenerator) {
        this.mTabTitleGenerator = mTabTitleGenerator
    }

    /**
     * Override this method if you want to use custom tab layout
     *
     * @param tab Tab
     */
    private fun onUpdateTab(tab: TabLayout.Tab) {
        if (tab.customView == null) {
            tab.customView = null // invokes update() method internally.
        }
    }

    //
    // internal methods
    //
    private fun handleOnDataSetChanged() {
        cancelPendingUpdateScrollPosition()
        cancelPendingSetTabsFromPagerAdapter()
        if (mSetTabsFromPagerAdapterRunnable == null) {
            mSetTabsFromPagerAdapterRunnable = Runnable {
                setTabsFromPagerAdapter(
                    mTabLayout!!,
                    mViewPager!!.adapter,
                    mViewPager!!.currentItem
                )
            }
        }
        mTabLayout!!.post(mSetTabsFromPagerAdapterRunnable)
    }

    private fun handleOnTabSelected(tab: TabLayout.Tab) {
        if (mDuringSetTabsFromPagerAdapter) {
            return
        }
        mViewPager!!.currentItem = tab.position
        cancelPendingUpdateScrollPosition()
    }

    private fun cancelPendingAdjustTabMode() {
        if (mAdjustTabModeRunnable != null) {
            mTabLayout!!.removeCallbacks(mAdjustTabModeRunnable)
            mAdjustTabModeRunnable = null
        }
    }

    private fun cancelPendingSetTabsFromPagerAdapter() {
        if (mSetTabsFromPagerAdapterRunnable != null) {
            mTabLayout!!.removeCallbacks(mSetTabsFromPagerAdapterRunnable)
            mSetTabsFromPagerAdapterRunnable = null
        }
    }

    private fun cancelPendingUpdateScrollPosition() {
        if (mUpdateScrollPositionRunnable != null) {
            mTabLayout!!.removeCallbacks(mUpdateScrollPositionRunnable)
            mUpdateScrollPositionRunnable = null
        }
    }

    private fun adjustTabMode(prevScrollX: Int) {
        val prevScrollXMinZero = if (prevScrollX < 0) mTabLayout!!.scrollX else prevScrollX
        if (mAdjustTabModeRunnable != null) {
            return
        }
        if (ViewCompat.isLaidOut(mTabLayout!!)) {
            adjustTabModeInternal(mTabLayout!!, prevScrollXMinZero)
        } else {
            mAdjustTabModeRunnable = Runnable {
                mAdjustTabModeRunnable = null
                adjustTabModeInternal(mTabLayout!!, prevScrollXMinZero)
            }
            mTabLayout!!.post(mAdjustTabModeRunnable)
        }
    }

    private fun createNewTab(tabLayout: TabLayout, position: Int): TabLayout.Tab {
        return onCreateTab(tabLayout, position)
    }

    private fun setupWithViewPager(tabLayout: TabLayout, viewPager: ViewPager2) {
        val adapter = viewPager.adapter
            ?: throw IllegalArgumentException("ViewPager does not have a PagerAdapter set")
        setTabsFromPagerAdapter(tabLayout, adapter, viewPager.currentItem)
        viewPager.adapter!!.registerAdapterDataObserver(mInternalDataSetObserver!!)
        viewPager.registerOnPageChangeCallback(mInternalTabLayoutOnPageChangeListener!!)
        tabLayout.addOnTabSelectedListener(mInternalOnTabSelectedListener)
    }

    private fun setTabsFromPagerAdapter(
        tabLayout: TabLayout,
        adapter: RecyclerView.Adapter<*>?,
        currentItem: Int
    ) {
        try {
            mDuringSetTabsFromPagerAdapter = true
            val prevScrollX = tabLayout.scrollX

            // remove all tabs
            tabLayout.removeAllTabs()

            // add tabs
            if (adapter != null) {
                val count = adapter.itemCount
                for (i in 0 until count) {
                    val tab = createNewTab(tabLayout, i)
                    tabLayout.addTab(tab, false)
                    updateTab(tab)
                }

                // select current tab
                val currentItemPosition = Math.min(currentItem, count - 1)
                val tab = tabLayout.getTabAt(currentItemPosition)
                if (currentItemPosition >= 0 && tab != null) {
                    tab.select()
                }
            }

            // adjust tab mode & gravity
            if (mAutoAdjustTabMode) {
                adjustTabMode(prevScrollX)
            } else {
                // restore scroll position if needed
                val curTabMode = tabLayout.tabMode
                if (curTabMode == TabLayout.MODE_SCROLLABLE) {
                    tabLayout.scrollTo(prevScrollX, 0)
                }
            }
        } finally {
            mDuringSetTabsFromPagerAdapter = false
        }
    }

    private fun updateTab(tab: TabLayout.Tab) {
        onUpdateTab(tab)
    }

    private fun determineTabMode(tabLayout: TabLayout): Int {
        val slidingTabStrip = tabLayout.getChildAt(0) as LinearLayout
        val childCount = slidingTabStrip.childCount

        // NOTE: slidingTabStrip.getMeasuredWidth() method does not return correct width!
        // Need to measure each tabs and calculate the sum of them.
        val tabLayoutWidth = tabLayout.measuredWidth - tabLayout.paddingStart - tabLayout.paddingEnd
        val tabLayoutHeight =
            tabLayout.measuredHeight - tabLayout.paddingTop - tabLayout.paddingBottom
        if (childCount == 0) {
            return TabLayout.MODE_FIXED
        }
        var stripWidth = 0
        var maxWidthTab = 0
        val tabHeightMeasureSpec =
            View.MeasureSpec.makeMeasureSpec(tabLayoutHeight, View.MeasureSpec.EXACTLY)
        for (i in 0 until childCount) {
            val tabView = slidingTabStrip.getChildAt(i)
            tabView.measure(View.MeasureSpec.UNSPECIFIED, tabHeightMeasureSpec)
            val tabWidth = tabView.measuredWidth
            stripWidth += tabWidth
            maxWidthTab = maxWidthTab.coerceAtLeast(tabWidth)
        }
        return if (stripWidth < tabLayoutWidth && maxWidthTab < (tabLayoutWidth / childCount)) TabLayout.MODE_FIXED else TabLayout.MODE_SCROLLABLE
    }

    private fun adjustTabModeInternal(tabLayout: TabLayout, prevScrollX: Int) {
        val prevTabMode = tabLayout.tabMode
        tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
        tabLayout.tabGravity = TabLayout.GRAVITY_CENTER
        val newTabMode = determineTabMode(tabLayout)
        cancelPendingUpdateScrollPosition()
        if (newTabMode == TabLayout.MODE_FIXED) {
            tabLayout.tabGravity = TabLayout.GRAVITY_FILL
            tabLayout.tabMode = TabLayout.MODE_FIXED
        } else {
            val slidingTabStrip = tabLayout.getChildAt(0) as LinearLayout
            slidingTabStrip.gravity = Gravity.CENTER_HORIZONTAL
            if (prevTabMode == TabLayout.MODE_SCROLLABLE) {
                // restore scroll position
                tabLayout.scrollTo(prevScrollX, 0)
            } else {
                // scroll to current selected tab
                mUpdateScrollPositionRunnable = Runnable {
                    mUpdateScrollPositionRunnable = null
                    updateScrollPosition()
                }
                mTabLayout!!.post(mUpdateScrollPositionRunnable)
            }
        }
    }

    private fun updateScrollPosition() {
        mTabLayout!!.setScrollPosition(mTabLayout!!.selectedTabPosition, 0f, false)
    }
}
